Skip to content

Support SLIP-132 descriptor imports - #13

Merged
praveenperera merged 3 commits into
masterfrom
slip132
Jun 18, 2026
Merged

praveenperera merged 3 commits into
masterfrom
slip132

Conversation

@praveenperera

@praveenperera praveenperera commented Jun 18, 2026 •

Copy link
Copy Markdown
Member

Summary

  • Normalize SLIP-132 public keys embedded in descriptor strings before Miniscript parsing.
  • Route single-line, two-line, and deserialized descriptor parsing through the same normalization path.
  • Add regression coverage for multipath and two-line descriptors that use zpub keys.

Context

PR #9 added SLIP-132 support for bare keys, JSON, Electrum, Wasabi, and BIP380 key-expression imports. It did not cover the raw descriptor-string case where a wallet/export uses a non-canonical ypub, zpub, upub, or vpub inside descriptor text.

Canonical descriptors should still use standard BIP32 xpub or tpub keys, with the script wrapper carrying the script type. This change accepts those non-canonical descriptor imports for compatibility, normalizes them before parsing, and keeps descriptor output canonical.

If normalization changes a descriptor that already has a checksum, the checksum is stripped before Miniscript parses the descriptor because replacing the embedded key invalidates the original checksum.

Validation

  • cargo fmt
  • cargo clippy
  • cargo test

Summary by CodeRabbit

  • New Features

    • Added support for normalizing SLIP-132 extended public key formats (zpub, ypub, upub, vpub) in descriptor parsing
    • Improved multipath descriptor parsing with enhanced checksum and variant key format handling
    • Optimized for zero-allocation parsing when no key format conversions are needed
  • Tests

    • Extended test suite with comprehensive multipath descriptor parsing test cases covering multiple key format variants

Normalize SLIP-132 public keys embedded in descriptor strings before parsing them with Miniscript. This keeps canonical output as xpub or tpub while accepting descriptor exports that use ypub, zpub, upub, or vpub.
@coderabbitai

coderabbitai Bot commented Jun 18, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@praveenperera, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 34 minutes and 8 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: da1471bc-d6ba-4c13-a26f-86f3dfcfa719

📥 Commits

Reviewing files that changed from the base of the PR and between bed2f09 and c9cf702.

📒 Files selected for processing (1)
  • src/xpub.rs
📝 Walkthrough

Walkthrough

Adds a normalize_slip132_public_keys function to src/xpub.rs that scans descriptor strings for SLIP-132 prefixes (ypub, zpub, upub, vpub) and replaces matched Base58Check tokens with their standard xpub/tpub forms. A new parse_descriptor_line helper in src/descriptor.rs uses this normalization and strips #checksum suffixes before parsing; all descriptor parsing entry points (try_from_line, TryFrom<&str>, deserialize_descriptor) now delegate to this helper.

Changes

SLIP-132 normalization and descriptor parsing

Layer / File(s) Summary
normalize_slip132_public_keys implementation
src/xpub.rs
Adds normalize_slip132_public_keys (public), internal helpers for SLIP-132 prefix detection, Base58 boundary/token-length scanning, and to_standard_extended_public_key replacement, returning Cow::Borrowed when no replacements occur. Adds a unit test asserting zpub→xpub rewriting.
parse_descriptor_line helper, wiring, and tests
src/descriptor.rs
Introduces parse_descriptor_line and normalized_descriptor_line helpers that normalize SLIP-132 keys and strip #checksum suffixes. Updates try_from_line, TryFrom<&str> two-line path, and serde deserialize_descriptor to delegate to parse_descriptor_line. Adds tests for zpub multipath descriptors with and without explicit checksum suffixes.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • bitcoinppl/pubport#6: Both PRs normalize SLIP-132 extended public keys (ypub/zpub → xpub/tpub) within descriptor parsing, the retrieved PR via try_from_single_sig key conversion.
  • bitcoinppl/pubport#9: The retrieved PR refactors the same src/xpub.rs SLIP-132 standardization path (to_standard_extended_public_key) that this PR's normalize_slip132_public_keys builds upon.
  • bitcoinppl/pubport#10: Both PRs modify Descriptors::try_from_line in src/descriptor.rs—this PR introduces the shared parse_descriptor_line helper, the retrieved PR routes the same entry point through a multipath/DescriptorBuilder construction path.

Poem

🐇 Hop, hop through the descriptor tree,
zpub spotted — can't fool me!
Strip the checksum, normalize the key,
xpub emerges, clean and free.
With Cow in paw and Base58 in sight,
This little rabbit parses it right! 🌟

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Support SLIP-132 descriptor imports' directly and clearly summarizes the main change: adding support for importing descriptors with SLIP-132 extended public keys (ypub, zpub, upub, vpub) by normalizing them before parsing.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch slip132

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@praveenperera
praveenperera marked this pull request as ready for review June 18, 2026 20:06
@greptile-apps

greptile-apps Bot commented Jun 18, 2026 •

Copy link
Copy Markdown

Greptile Summary

This PR extends SLIP-132 normalization to raw descriptor strings, covering the case PR #9 left out: wallet exports that embed ypub/zpub/upub/vpub keys directly inside descriptor text. All three descriptor-entry paths (single-line multipath, two-line split, JSON-deserialized) are now routed through a shared parse_descriptor_line helper that normalizes before parsing and strips any now-invalid checksum.

  • normalize_slip132_public_keys in xpub.rs is a linear scanner that replaces SLIP-132 key tokens in-place, returning Cow::Borrowed (zero allocation) when no substitution is needed.
  • normalized_descriptor_line in descriptor.rs strips the checksum from the normalized string only when a key was actually replaced (Cow::Owned), preserving existing checksums for already-canonical descriptors.
  • New tests exercise the multipath-zpub, multipath-zpub-with-checksum, and two-line-zpub paths.

Confidence Score: 5/5

Safe to merge. The normalization is scoped to SLIP-132 prefixes, leaves canonical xpub/tpub descriptors untouched, and the checksum-strip path is gated behind actual key substitution.

The Base58 alphabet in is_base58_char is correct (properly excludes 0, O, I, l). The Cow-based zero-copy path fires exactly when it should. Checksum stripping occurs only when normalize_slip132_public_keys returns Cow::Owned, so canonical descriptors are never affected. The new tests cover multipath-with-zpub, multipath-with-zpub-and-checksum, and two-line-zpub — the three previously untested entry points. No off-by-one, no unsafe indexing, no logic gaps found.

No files require special attention.

Important Files Changed

Filename Overview
src/xpub.rs Adds normalize_slip132_public_keys — a zero-copy scanner that replaces ypub/zpub/upub/vpub tokens with their canonical xpub/tpub equivalents. Helper functions are correct; the Base58 alphabet exclusions (0, O, I, l) match the spec. Returns Cow::Borrowed when no substitution occurs (no allocation) and Cow::Owned otherwise.
src/descriptor.rs Consolidates all descriptor-string parsing through parse_descriptor_line / normalized_descriptor_line. The checksum-strip logic (only on Cow::Owned) is correct. New tests cover the multipath zpub, zpub-with-checksum, and two-line-zpub paths that were previously untested.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["Descriptor string input"] --> B["normalized_descriptor_line()"]
    B --> C["normalize_slip132_public_keys()"]
    C --> D{Any SLIP-132 key found?}
    D -- "No (xpub/tpub)" --> E["Cow::Borrowed\n(unchanged string)"]
    D -- "Yes (ypub/zpub/upub/vpub)" --> F["Replace each key via\nto_standard_extended_public_key()"]
    F --> G["Cow::Owned\n(modified string)"]
    E --> H["Return original line\n(checksum preserved)"]
    G --> I{String contains '#'?}
    I -- "Yes (checksum present)" --> J["rsplit_once('#')\nStrip invalidated checksum"]
    I -- "No" --> K["Return normalized string\nwithout checksum"]
    J --> L["Return stripped descriptor"]
    H --> M["parse_descriptor_line()\nparse_descriptor(secp, line)"]
    L --> M
    K --> M
    M --> N["Descriptor<DescriptorPublicKey>"]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A["Descriptor string input"] --> B["normalized_descriptor_line()"]
    B --> C["normalize_slip132_public_keys()"]
    C --> D{Any SLIP-132 key found?}
    D -- "No (xpub/tpub)" --> E["Cow::Borrowed\n(unchanged string)"]
    D -- "Yes (ypub/zpub/upub/vpub)" --> F["Replace each key via\nto_standard_extended_public_key()"]
    F --> G["Cow::Owned\n(modified string)"]
    E --> H["Return original line\n(checksum preserved)"]
    G --> I{String contains '#'?}
    I -- "Yes (checksum present)" --> J["rsplit_once('#')\nStrip invalidated checksum"]
    I -- "No" --> K["Return normalized string\nwithout checksum"]
    J --> L["Return stripped descriptor"]
    H --> M["parse_descriptor_line()\nparse_descriptor(secp, line)"]
    L --> M
    K --> M
    M --> N["Descriptor<DescriptorPublicKey>"]
Loading

Reviews (3): Last reviewed commit: "Skip short SLIP-132 tokens when normaliz..." | Re-trigger Greptile

Comment thread src/descriptor.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/xpub.rs (1)

207-210: 💤 Low value

Consider adding a minimum token length check before conversion.

If a descriptor's checksum coincidentally starts with a SLIP-132 prefix (e.g., #zpubxxxx), the function would attempt to decode an 8-character token as an extended public key and fail. While extremely unlikely in practice (valid checksums are 8 chars; xpubs are 111 chars), adding a length guard would prevent false positives:

if token.len() < 100 {
    // Not a valid xpub length, skip normalization for this token
    index += next_char.len_utf8();
    continue;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/xpub.rs` around lines 207 - 210, The code currently attempts to convert
any token matching the SLIP-132 prefix check without verifying it is long enough
to be a valid extended public key. Add a length validation check after
determining the token and before calling to_standard_extended_public_key to
ensure the token is at least 100 characters long (the minimum length for a valid
xpub). If the token is shorter than this minimum length, skip the normalization
for that token and continue processing the next character by incrementing the
index and using continue.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/xpub.rs`:
- Around line 207-210: The code currently attempts to convert any token matching
the SLIP-132 prefix check without verifying it is long enough to be a valid
extended public key. Add a length validation check after determining the token
and before calling to_standard_extended_public_key to ensure the token is at
least 100 characters long (the minimum length for a valid xpub). If the token is
shorter than this minimum length, skip the normalization for that token and
continue processing the next character by incrementing the index and using
continue.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 34468579-13c5-4b67-91f0-a5001c31c72b

📥 Commits

Reviewing files that changed from the base of the PR and between 3f9b301 and bed2f09.

📒 Files selected for processing (2)
  • src/descriptor.rs
  • src/xpub.rs

Add a minimum encoded extended public key length check and related safety improvements in src/xpub.rs. Introduce MIN_ENCODED_EXTENDED_PUBLIC_KEY_LENGTH (100) and skip base58 tokens shorter than that to avoid misinterpreting invalid short tokens (e.g. "zpubINVALID"). Replace an expect() on next character with safe pattern matching to prevent panics at string boundaries. Add a unit test (test_normalize_slip132_public_keys_skips_short_tokens) to verify short tokens are ignored.
@praveenperera
praveenperera merged commit 17f263f into master Jun 18, 2026
10 checks passed
@praveenperera
praveenperera deleted the slip132 branch June 18, 2026 20:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant